﻿using Stardust.Common.Model;
using Stardust.Common.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TaskScheduler;

namespace Stardust.Client
{
    class NodeManager
    {

        static Scheduler m_scheduler;
        static NodeManager()
        {
            m_scheduler = new Scheduler();

            m_scheduler.AddJob("GetNodesUpdates", new SimpleTrigger(6, 0), (sch, trigger) =>
            {
                GetNodesUpdatesJob();
            });
            //m_scheduler.AddJob("RemoveDisabledNode", new SimpleTrigger(6, 0), (sch, trigger) =>
            //{
            //    RemoveDisabledNodeJob();
            //});
            m_scheduler.Start();
        }

        static ConcurrentDictionary<string, NodeGroupDTO> m_nodeDict = new ConcurrentDictionary<string, NodeGroupDTO>(StringComparer.OrdinalIgnoreCase);
        //static ConcurrentDictionary<long, DateTime> m_disabledNodeDict = new ConcurrentDictionary<long, DateTime>();
        //public static void AddDisabledNode(long nodeId)
        //{
        //    if (nodeId > 0)
        //    {
        //        m_disabledNodeDict[nodeId] = DateTime.Now;
        //    }
        //}
        //static void RemoveDisabledNodeJob()
        //{

        //}

        public static ServerNodeModel GetNode(string serviceName, string version)
        {
            NodeGroupDTO group;
            if (!m_nodeDict.TryGetValue(serviceName, out group))
            {
                group = LoadByServiceName(serviceName);
                m_nodeDict[serviceName] = group;
            }
            group.LastInvokeTime = DateTime.Now;
            return ChooseNode(group.Nodes, serviceName, version);
        }

        static ServerNodeModel ChooseNode(List<ServerNodeModel> nodes, string serviceName, string version)
        {
            if (nodes == null || nodes.Count == 0)
            {
                throw new Exception(string.Format("服务：【{0}】没有可用节点", serviceName));
            }
            var versionParts = version.Split('.');
            var v1 = versionParts[0];
            var v2 = versionParts[1];
            var compatibleVersionPrex = v1 + ".";
            var compatibleNodes = nodes.Where(x => x.Status == Common.Enum.ServerNodeStatus.Normal && x.Version.StartsWith(compatibleVersionPrex)).ToList();
            if (compatibleNodes.Count == 0)
            {
                throw new Exception(string.Format("服务：【{0}】没有兼容的节点版本【{1}】", serviceName, version));
            }
            List<ServerNodeModel> balanceNodes;
            if (v2 == "*")
            {
                balanceNodes = compatibleNodes.GroupBy(x => x.Version).OrderByDescending(x => int.Parse(x.Key.Remove(0, compatibleVersionPrex.Length))).First().ToList();
            }
            else if (v2.EndsWith("+"))
            {
                var intV2 = int.Parse(v2.TrimEnd('+'));
                balanceNodes = compatibleNodes.Where(x => int.Parse(x.Version.Remove(0, compatibleVersionPrex.Length)) >= intV2).ToList();
            }
            else if (v2.EndsWith("-"))
            {
                var intV2 = int.Parse(v2.TrimEnd('-'));
                balanceNodes = compatibleNodes.Where(x => int.Parse(x.Version.Remove(0, compatibleVersionPrex.Length)) <= intV2).ToList();
            }
            else if (v2.EndsWith(">"))
            {
                var intV2 = int.Parse(v2.TrimEnd('>'));
                balanceNodes = compatibleNodes.Where(x => int.Parse(x.Version.Remove(0, compatibleVersionPrex.Length)) > intV2).ToList();
            }
            else if (v2.EndsWith("<"))
            {
                var intV2 = int.Parse(v2.TrimEnd('<'));
                balanceNodes = compatibleNodes.Where(x => int.Parse(x.Version.Remove(0, compatibleVersionPrex.Length)) < intV2).ToList();
            }
            else
            {
                balanceNodes = compatibleNodes.Where(x => x.Version == version).ToList();
            }
            //return balanceNodes;
            return BalanceNode(balanceNodes, serviceName, version);
        }

        static ServerNodeModel BalanceNode(List<ServerNodeModel> nodes, string serviceName, string version)
        {
            if (nodes.Count == 0)
            {
                throw new Exception(string.Format("服务：【{0}】没有兼容的节点版本【{1}】", serviceName, version));
            }
            if (nodes.Count == 1) return nodes[0];
            var averageWeight = 1d / nodes.Count;
            var totalWeight = 0d;
            //处理权重 ,DynamicWeight
            nodes.ForEach(x =>
            {
                if (x.Weight <= 0)
                {
                    x.DynamicWeight = averageWeight;
                    totalWeight += averageWeight;
                }
                else
                {
                    x.DynamicWeight = x.Weight;
                    totalWeight += x.Weight;
                }
            });
            //权重从小到大排序 ,傻了，好像不用排序
            //nodes.Sort((n1, n2) =>
            //{
            //    if (n1.DynamicWeight > n2.DynamicWeight)
            //    {
            //        return 1;
            //    }
            //    else if (n1.DynamicWeight < n2.DynamicWeight)
            //    {
            //        return -1;
            //    }
            //    else
            //    {
            //        return 0;
            //    }
            //});
            var rateArray = nodes.Select(x => x.DynamicWeight / totalWeight).ToArray();
            Random r = new Random();
            var point = r.NextDouble();
            var range = 0d;
            for (int i = 0; i < rateArray.Length; i++)
            {
                range += rateArray[i];
                if (point < range)
                {
                    return nodes[i];
                }
            }
            return nodes[rateArray.Length - 1];
        }

        static NodeGroupDTO LoadByServiceName(string serviceName)
        {
            var data = new Dictionary<string, string>()
            {
                { "serviceName", serviceName}
            };

            var task = HttpHelper.PostJsonToUrlAsync(StardustClient.GetNodesByNameUrl(), data);
            try
            {
                task.Wait();
                if (string.IsNullOrEmpty(task.Result))
                {
                    return new NodeGroupDTO { Nodes = new List<ServerNodeModel>() };
                }
                return SerializerHelper.DeserializeFromString<NodeGroupDTO>(task.Result);
            }
            catch (Exception exp)
            {
                //log
                return new NodeGroupDTO { Nodes = new List<ServerNodeModel>() };
            }
        }

        static void GetNodesUpdatesJob()
        {
            var data = m_nodeDict
                .Where(x => x.Value.LastInvokeTime > DateTime.Now.AddDays(-1))
                .Select(x => new GetNodesUpdateParams
                {
                    ServiceName = x.Key,
                    MaxEventId = x.Value.MaxEventId
                })
                .ToList();
            if (data.Count > 0)
            {
                HttpHelper.PostJsonToUrlAsync(StardustClient.GetNodesUpdatesUrl(), data).ContinueWith(task =>
                {
                    if (!task.IsFaulted && !string.IsNullOrEmpty(task.Result))
                    {
                        var result = SerializerHelper.DeserializeFromString<GetNodesUpdateResult>(task.Result);
                        ApplyEvents(result);
                    }
                    else
                    {
                        var exp = task.Exception.GetBaseException();
                        //log
                    }
                });
            }
        }

        /// <summary>
        /// 应用事件
        /// </summary>
        /// <param name="result"></param>
        static void ApplyEvents(GetNodesUpdateResult result)
        {
            var events = result.EventList;
            if (events != null && events.Count > 0)
            {
                foreach (var evt in events)
                {
                    NodeGroupDTO group;
                    if (!m_nodeDict.TryGetValue(evt.ServerNode.ServiceName, out group))
                    {
                        group = new NodeGroupDTO();
                        group.Nodes = new List<ServerNodeModel>();
                        m_nodeDict[evt.ServerNode.ServiceName] = group;
                    }
                    group.MaxEventId = result.MaxEventId;
                    var localNode = group.Nodes.FirstOrDefault(x => x.Id == evt.ServerNodeId);
                    if (localNode != null)
                    {
                        switch (evt.EventType)
                        {
                            case Common.Enum.NodeEventType.Logout:
                            case Common.Enum.NodeEventType.Delete:
                                localNode.Status = Common.Enum.ServerNodeStatus.Disabled;
                                break;
                            case Common.Enum.NodeEventType.Update:
                            case Common.Enum.NodeEventType.Register:
                                localNode.Status = evt.ServerNode.Status;
                                localNode.Address = evt.ServerNode.Address;
                                localNode.Version = evt.ServerNode.Version;
                                localNode.Weight = evt.ServerNode.Weight;
                                break;
                            default:
                                break;
                        }
                    }
                    else
                    {
                        if (evt.EventType == Common.Enum.NodeEventType.Register || evt.EventType == Common.Enum.NodeEventType.Update)
                        {
                            group.Nodes.Add(evt.ServerNode);
                        }
                    }
                }
            }
        }
    }
}
